package com.google.code.stalkernet.example.ui;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;

import com.google.code.stalkernet.async.io.AsyncSocketChannel;
import com.google.code.stalkernet.node.Node;
import com.google.code.stalkernet.node.event.NodeEvent;
import com.google.code.stalkernet.node.event.NodeListener;
import com.google.code.stalkernet.peer.AsyncSocketChannelPeer;
import com.google.code.stalkernet.peer.Peer;
import com.google.code.stalkernet.reactor.Reactor;

public class PeersTableModel
    extends AbstractTableModel implements NodeListener
{
    private final Reactor reactor;
    private final Node node;

    private final List <Peer> peers = new ArrayList <> ();

    public PeersTableModel (Reactor reactor, Node node)
    {
        if (reactor == null)
            throw new IllegalArgumentException ("Reactor is null");

        if (node == null)
            throw new IllegalArgumentException ("Node is null");

        this.reactor = reactor;
        this.node = node;

        reactor.invoke (new DumpPeersRunnable ());
    }

    public Peer getPeerAt (int index)
    {
        return peers.get (index);
    }

    @Override
    public int getRowCount ()
    {
        return peers.size ();
    }

    @Override
    public int getColumnCount ()
    {
        return 4;
    }

    @Override
    public Object getValueAt (int rowIndex, int columnIndex)
    {
        try
        {
            AsyncSocketChannelPeer peer = (AsyncSocketChannelPeer)peers.get (rowIndex);
            AsyncSocketChannel socketChannel = peer.getAsyncSocketChannel ();
            InetSocketAddress localSocketAddress =
                (InetSocketAddress)socketChannel.getLocalAddress ();
            InetSocketAddress remoteSocketAddress =
                (InetSocketAddress)socketChannel.getRemoteAddress ();

            switch (columnIndex)
            {
            case 0:
                return localSocketAddress.getAddress ();
            case 1:
                return Integer.valueOf (localSocketAddress.getPort ());
            case 2:
                return remoteSocketAddress.getAddress ();
            case 3:
                return Integer.valueOf (remoteSocketAddress.getPort ());
            default:
                throw new IllegalArgumentException (
                    "Invalid column index: " + columnIndex);
            }
        }
        catch (IOException ex)
        {
            return null;
        }
    }

    @Override
    public String getColumnName (int column)
    {
        switch (column)
        {
        case 0:
            return "Local Address";
        case 1:
            return "Local Port";
        case 2:
            return "Remote Address";
        case 3:
            return "Remote Port";
        default:
            throw new IllegalArgumentException (
                "Invalid column: " + column);
        }
    }

    @Override
    public void peerAdded (NodeEvent nodeEvent)
    {
        final Peer peer = nodeEvent.getPeer ();

        SwingUtilities.invokeLater (
            new Runnable ()
            {
                @Override
                public void run ()
                {
                    int index = peers.size ();
                    peers.add (index, peer);
                    fireTableRowsInserted (index, index);
                }
            });
    }

    @Override
    public void peerRemoved (NodeEvent nodeEvent)
    {
        final Peer peer = nodeEvent.getPeer ();

        SwingUtilities.invokeLater (
            new Runnable ()
            {
                @Override
                public void run ()
                {
                    int index = peers.indexOf (peer);
                    peers.remove (index);
                    fireTableRowsDeleted (index, index);
                }
            });
    }

    private class DumpPeersRunnable implements Runnable
    {
        @Override
        public void run ()
        {
            SwingUtilities.invokeLater (
                new AddPeersRunnable (node.getAllPeers ()));
            node.addNodeListener (PeersTableModel.this);
        }
    }

    private class AddPeersRunnable implements Runnable
    {
        private final Peer [] peers;

        public AddPeersRunnable (Peer [] peers)
        {
            if (peers == null)
                throw new IllegalArgumentException ("Peers is null");

            this.peers = peers.clone ();
        }

        @Override
        public void run ()
        {
            int peersLength = peers.length;

            if (peersLength > 0)
            {
                int index = PeersTableModel.this.peers.size ();
                PeersTableModel.this.peers.addAll (Arrays.asList (peers));
                fireTableRowsInserted (index, index + peersLength - 1);
            }
        }
    }
}
